
import { Canvas, find, MeshRenderer, Sprite } from 'cc';
import { _decorator, Component, Node , EventTouch, Touch,Vec2, Vec3, tween, Camera, input, Input, geometry, PhysicsSystem, Graphics, view} from 'cc';
import { App } from '../../App';
import { EventConst } from '../../common/Common';
import UtilTool from '../../tools/UtilTool';
import { Tween } from 'cc';
import { GameBaseModel } from './GameBaseModel';
import { GAME_MULTI_TYPE, MAX_FORCE, MAX_FORCE_BOMB } from './GameManage';
import { GameModel3 } from './mode3/GameModel3';
import { Ball } from './Ball';
import { GameModel2 } from './mode2/GameModel2';
import { PD } from '../../player/PlayerData';
import { GameModel4 } from './mode4/GameModel4';
import { PlayBaseUser } from './PlayBaseUser';
import { GameModel5 } from './mode5/GameModel5';
import { GameBaseLogic } from './GameBaseLogic';
const { ccclass, property } = _decorator;

// 球杆
@ccclass('Cue')
export class Cue extends Component {
    @property(Node)
    cueNode: Node = null;

    // @property(Node)
    whiteBall: Node = null;

    // @property(Node)
    virtualball: Node = null;

    cameraUI: Camera = null
    camera3D: Camera = null
    graphic_line: Graphics = null
    
    _mouseDown = false  // 是否点击了杆子
    cueStartPos = -50  // 杆子初始Z轴位置
    cueMaxForce = 400    // 杆子最大的受力
    // cuePosY = 10        // 杆子的高度
    cueAngle = 0        // 杆子的操作对应球的角度
    last_cueAngle = 0   // 上一次杆子对应球的角度
    lastWorldP = null   // 滑动杆子上一次的3D世界坐标
    isPullCue = false   // 是否可以拉杆

    moveAngleforone = 0.09  // 一点一个像素点对应杆移动的角度数
    last3DPos = null        // 手指触摸屏幕的上一次3D世界坐标点

    lineType = 3    // [0] 没有虚拟白球的白球瞄准线 [1] 没有虚拟白球的目标球瞄准线 [2]有虚拟白球的白球瞄准线 [3]有虚拟白球的目标球瞄准线 

    isMine = true   // 是否是自己的回合
    isHitBall = false // 是否能击球

    cueNumber = PD.user.defaultCue   // 当前球杆编号

    onLoad() {
        
    }

    start() {
        //触摸事件 Node.EventType.TOUCH_MOVE    MOUSE_MOVE
        input.on(Input.EventType.TOUCH_START, this.onTouchStart, this);
        input.on(Input.EventType.TOUCH_MOVE, this.onTouchMove, this);
        input.on(Input.EventType.TOUCH_CANCEL, this.onTouchCanel, this);
        input.on(Input.EventType.TOUCH_END, this.onTouchEnd, this);
        
        // 注册杆子的点击事件
        if (this.isPullCue) {
            input.on(Input.EventType.TOUCH_START, this.touchStartCue, this);
            input.on(Input.EventType.TOUCH_MOVE, this.touchMoveCue,  this);
            input.on(Input.EventType.TOUCH_CANCEL, this.touchCanelCue,  this);
            input.on(Input.EventType.TOUCH_END, this.touchEndCue,  this);
        }

        this.cameraUI = find("Canvas/Camera").getComponent(Camera)
        this.camera3D = find("Main Camera").getComponent(Camera)
        this.graphic_line = find("Canvas/ballGraphics").getComponent(Graphics)
        this.whiteBall = find("gameNode/game_3d/whiteball")
        this.virtualball = find("gameNode/game_3d/virtualball")
        
        // App.Event.on(EventConst.ALLBALL_SLEEP, this.onwballSleep, this)
        // setTimeout(this.autoShowLine.bind(this),1000)
        
        this.virtualball.active = false
        this.isHitBall = false

        let virtualModel = this.virtualball.getChildByName("model")
        let material = virtualModel.getComponent(MeshRenderer).getMaterial(0)
        material.setProperty("opacity", 0.3)

        this.node.setSiblingIndex(100)
    }

    update(deltaTime: number) {
        
    }

    /**
     * 开始点击屏幕
      */
    onTouchStart(event: EventTouch): void{
        //按下鼠标时，球杆方向不再移动。球杆隐藏时操作无效
        // if (this._mouseDown || !this.node.active || !this.isMine) {
        //     return;
        // }
        // if (this.checkTouchCue(event) && this.isPullCue) {
        //     return
        // }

        // // 获取世界坐标
        // let worldPoint = this.screenToWorld(event.getLocation()) 
        // let angle = this.getAngleByWorld(this.whiteBall.worldPosition, worldPoint)
        // this.cueAngle = angle
        // angle = this.ballToCueAngle(angle)
        // // UtilTool.printLog("angle:",angle)
        
        // // //设置球杆的角度 
        // this.node.setRotationFromEuler(new Vec3(0,angle,0))   
        // this.calculatePos(event,null)

        let screenPos = event.getLocation()
        this.last3DPos = this.screenToWorld(screenPos)
        this.last_cueAngle = this.cueAngle
    }

    /**
     * 移动屏幕
      */
    onTouchMove (event: EventTouch) {
        // //按下鼠标时，球杆方向不再移动。球杆隐藏时操作无效
        if (this._mouseDown || !this.node.active || !this.isMine) {
            return;
        }

        if (this.checkTouchCue(event) && this.isPullCue) {
            return
        }

        // 一方法（以球的上下左右为标准）
        /* let uidelta = event.getUIDelta()
        let uiPos = event.getUILocation()
        let uiPos_white = this.World3DToUI(this.whiteBall.worldPosition)
        let dis = UtilTool.getDistance(new Vec2(0,0), uidelta)
        let angle = this.moveAngleforone * dis

        if (uiPos.x > uiPos_white.x) {
            if (uiPos.y > uiPos_white.y) {  // 右上
                if (uidelta.x<0 || uidelta.y>0){
                    angle = -angle
                }
            }
            else {
                if (uidelta.x>0 || uidelta.y>0){  // 右下
                    angle = -angle
                }
            }
        }
        else {
            if (uiPos.y > uiPos_white.y) {  // 左上
                if (uidelta.x<0 || uidelta.y<0){
                    angle = -angle
                }
            }
            else {
                if (uidelta.x>0 || uidelta.y<0){    // 左下 
                    angle = -angle
                }
            }
        } */

        // 二方法（以球为中心点）
        // 计算滑动的角度
        let screenPos = event.getLocation()
        let pos3D = this.screenToWorld(screenPos)
        
        let angle1 = UtilTool.getPointAngle(new Vec2(this.whiteBall.worldPosition.x, this.whiteBall.worldPosition.z), new Vec2(this.last3DPos.x, this.last3DPos.z))
        angle1 = UtilTool.getRAngle(angle1)
        let angle2 = UtilTool.getPointAngle(new Vec2(this.whiteBall.worldPosition.x, this.whiteBall.worldPosition.z), new Vec2(pos3D.x, pos3D.z))
        angle2 = UtilTool.getRAngle(angle2)
        let angle = angle2 - angle1
        this.last3DPos = pos3D

        this.setCueRotation(angle)


        // 三方法（以触摸点为标准）
        // 获取世界坐标
        /* let worldPoint = this.screenToWorld(event.getLocation()) 
        let angle = this.getAngleByWorld(this.whiteBall.worldPosition, worldPoint)
        this.cueAngle = angle
        angle = this.ballToCueAngle(angle)
        // UtilTool.printLog("angle:",angle)

        // //设置球杆的角度 
        this.node.setRotationFromEuler(new Vec3(0,angle,0))
        this.calculatePos(event,null) */
    }

    /**
     * 设置杆子旋转
      */
    setCueRotation(angle) {
        // 杆子旋转角度
        this.cueAngle = this.cueAngle + angle
        angle = this.ballToCueAngle(this.cueAngle)
        this.node.setRotationFromEuler(new Vec3(0,0,angle))

        // 画线
        let startPoint = new Vec2(this.whiteBall.worldPosition.x, this.whiteBall.worldPosition.z)   // 开始点
        let endPoint = UtilTool.getPointMove2(startPoint, this.cueAngle, 100)
        this.calculatePos(null, new Vec3(endPoint.x, 0, endPoint.y))
    }

    /**
     * 取消点击屏幕
      */
    onTouchCanel(event: EventTouch): void{
        if (this.last_cueAngle != this.cueAngle) {
            let gameView = App.Model.gameView as GameBaseModel
            gameView.reqChangeCueMsg(this.ballToCueAngle(this.cueAngle))
            this.last_cueAngle = this.cueAngle
        }
    }

    /**
     * 结束点击屏幕
      */
    onTouchEnd(event: EventTouch): void{
        if (this.last_cueAngle != this.cueAngle) {
            let gameView = App.Model.gameView as GameBaseModel
            gameView.reqChangeCueMsg(this.ballToCueAngle(this.cueAngle))
            this.last_cueAngle = this.cueAngle
        }
    }

    /**
     * 监听白球停止时，显示球杆
     * @param isMine 是否是自己操作
     * @param initCueAngle 初始化球杆角度
      */
    showCue (isMine=true, initCueAngle = 0) {
        this.isHitBall = true
        this.node.active = true

        // 设置球杆的位置
        let whiteBallLocalPos = this.World3DToLocal(this.whiteBall.worldPosition)
        this.node.setPosition(whiteBallLocalPos.x, whiteBallLocalPos.y, 0)
        this.cueNode.setPosition(0,this.cueStartPos,0)
        
        this.whiteBall.setRotationFromEuler(new Vec3(0,0,0))
        this.node.setRotationFromEuler(new Vec3(0,0,initCueAngle))

        this.cueAngle = this.cueToBallAngle(initCueAngle)
        this.isMine = isMine
        setTimeout(this.autoShowLine.bind(this),100)
        this.setCueSprite(this.isMine)
        
        
        this.clearState()
        
    }

    /**
     * 摆放球杆
     * @param cueAngle 球杆的角度
      */
    changeCue(cueAngle) {
        this.node.active = true;
        this.node.setRotationFromEuler(new Vec3(0,0,cueAngle))
        this.cueAngle = this.cueToBallAngle(cueAngle)
        this.autoShowLine()
    }

    // 清除状态
    clearState() {

        if(this.graphic_line) {
            this.graphic_line.clear()
        }

        this.virtualball.active = false
    }

    // 设置球杆贴图
    setCueSprite(isMine) {
        let cueNumber = PD.user.defaultCue
        if (App.fightMode == App.fightMode_1 || App.fightMode == App.fightMode_2) { // 幸运台球、练习场
            if (isMine) {
                cueNumber = PD.user.currentCue
            }else{
                cueNumber = PD.user.defaultCue
            }
        }
        else {
            let gameLogic = App.Model.gameLogic as GameBaseLogic

            if (gameLogic==null || gameLogic.curPlayUser==null) {
                // 这种情况是还没有初始化完界面的时候走了这里
                if (isMine) {
                    cueNumber = PD.user.currentCue
                }else{
                    cueNumber = PD.user.defaultCue
                }
            }else{
                let curPlayUser: PlayBaseUser = gameLogic.curPlayUser
                cueNumber = App.Model.gameManage.getPlayerCueNumber(curPlayUser.userAddress)
            }
        }

        if (this.cueNumber == cueNumber) {
            return
        }

        let cueDict = App.Scene.getRes("images/gameCue")
        this.cueNode.getComponent(Sprite).spriteFrame = cueDict["cue_" + cueNumber]
        this.cueNumber = cueNumber
    }
    
    
///////////////////////////////////////////////////////////////////////////////////// 球杆 /////////////////////////////////////////////////////////////////////////////////////
    /**
     * 开始点击球杆
      */
    touchStartCue (event: EventTouch) {
        this.lastWorldP = this.screenToWorld(event.getLocation())
        
        //球杆隐藏时操作无效
        if (!this.node.active || !this.isMine) {
            return;
        }
        
        if (this.checkTouchCue(event)) {
            //将按下鼠标的标记设置为true
            this._mouseDown = true;
        }
        
    }

    /**
     * 移动球杆
      */
    private _ray: geometry.Ray = new geometry.Ray();
    touchMoveCue (event: EventTouch) {
        //球杆隐藏时操作无效
        if (!this.node.active || !this.isMine) {
            return;
        }

        if (this.checkTouchCue(event)) {
            //将按下鼠标的标记设置为true
            this._mouseDown = true;
            let worldPoint = this.screenToWorld(event.getLocation()) 
            let angle = this.getAngleByWorld(this.lastWorldP, worldPoint)
            let moveDis = UtilTool.getDistance(new Vec2(this.lastWorldP.x, this.lastWorldP.z), new Vec2(worldPoint.x, worldPoint.z))
            // UtilTool.printLog("this.cueAngle=",this.cueAngle,"angle=",angle)

            if (Math.abs(this.cueAngle - angle) <= 30) {
                // UtilTool.printLog("负方向移动了 <<<<<")
            }
            else {
                // UtilTool.printLog("正方向移动了 >>>>>")
                moveDis = -moveDis
            }
    
            let posZ = moveDis + this.cueNode.getPosition().y
            //使球杆向后移动
            // UtilTool.printLog("移动球杆距离",moveDis)
            if (posZ>this.cueStartPos) {
                posZ = this.cueStartPos
            }
            if (posZ<this.cueStartPos - this.cueMaxForce) {
                posZ = this.cueStartPos - this.cueMaxForce
            }

            this.cueNode.setPosition(0,posZ,0)
            this.lastWorldP = this.screenToWorld(event.getLocation())
        }
    }

    /**
     * 移动能量条回调
     * @param bfb 移动的百分比
      */
    moveEneryBarCallBack(bfb) {
        this.cueNode.setPosition(0,this.cueStartPos - this.cueMaxForce*bfb,0)
    }

    /**
     * 结束操作球杆
      */
    touchEndCue (event: EventTouch) {

        if(!this.isMine) {
            return
        }

        let cuePosZ = this.cueNode.getPosition().y
        if (!this._mouseDown || cuePosZ>this.cueStartPos-5) {
            this._mouseDown = false
            this.cueNode.setPosition(0,this.cueStartPos,0)
            return
        }
        
        this.onMouseUp()
    }

    /**
     * 取消操作球杆
      */
    touchCanelCue(event: EventTouch) {

        if(!this.isMine) {
            return
        }

        let cuePosZ = this.cueNode.getPosition().y
        if (!this._mouseDown || cuePosZ>this.cueStartPos+5) {
            this._mouseDown = false
            this.cueNode.setPosition(0,this.cueStartPos,0)
            return
        }
        
        this.onMouseUp()
    }

    /**
     * 操作球杆结束回调 (自己操作球杆击打球)
      */
    onMouseUp () {
        //球杆隐藏时操作无效
        if (!this.node.active) {
            return;
        }

        //计算球杆向后移动的像素，通过这个值来计算击球的力度
        var length = this.cueNode.getPosition().y-this.cueStartPos
        var force = Math.abs(length / this.cueMaxForce * this.getCueMaxForce())

        let gameView = App.Model.gameView as GameBaseModel
        //  处理一开始开球的时候力度最大值为MAX_FORCE_BOMB
        if (gameView.isOpenBallStatus) {
            force = force/this.getCueMaxForce()*MAX_FORCE_BOMB
        }

        // 请求击打球
        if (App.fightMode == App.fightMode_3 || App.fightMode == App.fightMode_4) {
            gameView.reqHitballMsg(this.cueAngle, force)

            /* setTimeout(() => { // 这里改为需要等到服务器通知击球才会去走击球
                if (this && this.node) {
                    this.cueAction(this.cueAngle, force) 
                }
            }, 200); */
        }
        else {
            this.cueAction(this.cueAngle, force) 
        }

        // 隐藏呼吸效果(只有练习房、多人房才有呼吸效果)
        if (App.fightMode == App.fightMode_2 || App.fightMode == App.fightMode_3) {
            App.Event.emit(EventConst.GAME_ABEL_BALL, false)
        }
    }

    // 杠击打动作（最后统一走这里去执行击打）
    cueAction(cueAngle, force) {
        let that = this

        UtilTool.printLog("cur cueAction", cueAngle, force)

        //使用序列动作，先执行
        tween()
        .target(this.cueNode)
        .to(0.2,{ position: new Vec3(0, this.cueStartPos, 0) })
        .call(() => {
            that._mouseDown = false;
            this.graphic_line.clear()
            this.virtualball.active = false

            // if (!this.isHitBall) {
            //     UtilTool.printLog("非击球状态")
            //     return
            // }

            let volume = force/this.getCueMaxForce()
            if (volume>1) volume=1
            App.Audio.playOneShot("cue",true,volume)
            App.Event.emit(EventConst.GAME_CUE_END, {"force":force, "angle": cueAngle, "isMine": this.isMine})
            that.node.active = false
        })
        .start()
    }

    // 根据力度往后移动，然后释放球杆完成击球动作(外部条件迫使击打球)
    // cueAngle 这个角度传的是球之间的角度
    cueMoveAction(cueAngle, force, putCuetime = 1) {
        this.cueAngle = cueAngle
        let angle = this.ballToCueAngle(cueAngle)

        let length = force / this.getCueMaxForce() * this.cueMaxForce
        if (length>this.cueMaxForce) {
            length = this.cueMaxForce
        }
        this.node.setRotationFromEuler(new Vec3(0,0,angle))

        tween().target(this.cueNode).to(putCuetime, { position: new Vec3(0, this.cueStartPos-length, 0) }).call(()=>{
            this.cueAction(cueAngle, force)
        }).start()
        
    }

    stopAllAction() {
        Tween.stopAllByTarget(this.cueNode)
        Tween.stopAllByTarget(this)
        Tween.stopAllByTarget(this.node)
    }

    /**   
     * 重置球杆的位置
    */
    resetCuePos() {
        this.cueNode.setPosition(new Vec3(0, this.cueStartPos, 0))
    }

///////////////////////////////////////////////////////////////////////////////////// 球杆 /////////////////////////////////////////////////////////////////////////////////////
    /**   
     * 屏幕转3D世界坐标
    */
    screenToWorld(screenPos:Vec2) {
        let c_z = this.camera3D.node.worldPosition.y / this.camera3D.getComponent(Camera).far
        let pos = new Vec3()
        this.camera3D.screenToWorld(new Vec3(screenPos.x, screenPos.y, c_z), pos)
        return pos
    }

    /**   
     * 3D世界坐标转UI坐标
    */
    World3DToUI(worldPos:Vec3) {
        let sceenPoint = this.camera3D.worldToScreen(worldPos);
        let uiPos = this.cameraUI.screenToWorld(sceenPoint);
        return new Vec2(uiPos.x, uiPos.y)
    }

    /**   
     * 3D世界坐标转本地坐标
    */
    World3DToLocal(worldPos:Vec3) {
        let sceenPoint = this.camera3D.worldToScreen(worldPos);
        let uiPos = this.cameraUI.screenToWorld(sceenPoint);

        let vs = view.getVisibleSize()
        return new Vec2(uiPos.x-vs.width/2, uiPos.y-vs.height/2)
    }

    /**   
     * 本地坐标转3D世界坐标
    */
    localToWorld3D(localPos:Vec2) {
        let vs = view.getVisibleSize()
        let uiPos = new Vec2(localPos.x+vs.width/2, localPos.y+vs.height/2)
        let sceenPoint = this.cameraUI.worldToScreen(new Vec3(uiPos.x, uiPos.y ,0))
        return this.screenToWorld(new Vec2(sceenPoint.x ,sceenPoint.y))
    }

    /**   
     * 通过世界坐标获取两点间的角度
    */
    getAngleByWorld(worldPos1:Vec3, worldPos2:Vec3) {
        let angle = UtilTool.getPointAngle(new Vec2(worldPos1.x, worldPos1.z), new Vec2(worldPos2.x, worldPos2.z))
        angle = UtilTool.getRAngle(angle)

        return angle
    }

    /**   
     * 通过UI坐标获取两点间的角度
    */
    getAngleByUI(worldPos1:Vec2, worldPos2:Vec2) {
        let angle = UtilTool.getPointAngle(new Vec2(worldPos1.x, worldPos1.y), new Vec2(worldPos2.x, worldPos2.y))
        angle = UtilTool.getRAngle(angle)

        return angle
    }
    
    /**   
     * 检测是否点击了球杆
    */
    checkTouchCue(event) {
        const touch = event.touch!;
        this.camera3D.screenPointToRay(touch.getLocationX(), touch.getLocationY(), this._ray);
        if (PhysicsSystem.instance.raycast(this._ray)) {
            const raycastResults = PhysicsSystem.instance.raycastResults;
            for (let i = 0; i < raycastResults.length; i++) {
                const item = raycastResults[i];
                if (item.collider.node == this.cueNode) {
                    // UtilTool.printLog('raycast hit the target node !');
                    return true
                }
            }
        }
        else {
            // UtilTool.printLog('raycast does not hit the target node !');
        }

        return false
    }

    /**   
     * 开始自动显示瞄准线
    */
    autoShowLine() {
        if (this.node && this.node.active && this.isMine && this.whiteBall) {
            let startPoint = new Vec2(this.whiteBall.worldPosition.x, this.whiteBall.worldPosition.z)   // 开始点
            let endPoint = UtilTool.getPointMove2(startPoint, this.cueAngle, 100)
            this.calculatePos(null, new Vec3(endPoint.x, 0, endPoint.y))
        }
    }

    /**   
     * 计算瞄准线坐标
     * @param endpoint 如果有值，则优先用这个结束点
    */
    isDrawLine = true   // 是否可以画瞄准线
    calculatePos(event: EventTouch,endpoint:Vec3) {
        this.graphic_line.clear()
        this.isDrawLine = true

        let endPoint = null
        if (endpoint) {
            endPoint = endpoint
        }
        else {
            let screenPos = event.getLocation() // 屏幕坐标
            endPoint = this.screenToWorld(screenPos)  // 结束点
        }

        let startPoint = this.whiteBall.worldPosition   // 开始点
        endPoint.y = startPoint.y

        if (this.lineType==0) {
            // 没有虚拟白球的逻辑
            let vector = new Vec3(endPoint.x-startPoint.x, 0, endPoint.z-startPoint.z)
            this.drawRayCast(startPoint, vector)
        }
        else if (this.lineType==1) {
            // 没有虚拟白球的逻辑
        }
        else if (this.lineType==2) {
            // 有虚拟白球的逻辑
            this.calculateVirtualWball(startPoint, endPoint)
        }
        else if (this.lineType==3) {
            // 有虚拟白球的逻辑
            this.calculateVirtualWball(startPoint, endPoint)
        }
    }

    /**
     * @description 检测射线技术
     * @param startLocation 起始位置 世界坐标系
     * @param vector_dir 方向向量
     */
    private drawRayCast(startPoint:Vec3, vector_dir:Vec3) {
        if (vector_dir.x==0 && vector_dir.z==0) return  // 点击起点不用处理
        
        let tempStartPoint = new Vec3(startPoint.x, startPoint.y, startPoint.z) // 临时复制一个起始点(因为startPoint总共起始点用作射线检测后会变成检测后中点的位置，还不知道为啥会变)
        let outRay = new geometry.Ray()
        let endPoint = null // 射线第一个碰撞体的3D世界坐标
        let hitNormal = null // 法线
        
        // 根据起点和方向创建射线
        outRay = new geometry.Ray(startPoint.x, startPoint.y, startPoint.z, vector_dir.x, vector_dir.y, vector_dir.z);
        if (PhysicsSystem.instance.raycastClosest(outRay)) {
            const raycastClosestResult = PhysicsSystem.instance.raycastClosestResult;
            const item = raycastClosestResult
            if (item) {
                if (item.collider.node.name == "ball") {    // 球
                }
                else if (item.collider.node.name == "wall") { // 墙
                }
                else if (item.collider.node.name == "edge_role") { // 球袋
                }
                endPoint = item.hitPoint
                hitNormal = item.hitNormal  // 法线(单位向量)
            }
        }

        // 有检测到射中目标
        if (hitNormal) {
            // 先画当前瞄准线
            this.drawAimLine(this.World3DToLocal(tempStartPoint),this.World3DToLocal(endPoint))

            // 继续计算下一条瞄准线
            this.isDrawLine = false
            if (this.isDrawLine) {
                // 入射向量
                let vector_in:Vec3 = new Vec3(endPoint.x-tempStartPoint.x, tempStartPoint.y, endPoint.z-tempStartPoint.z)

                // 这里计算法向量大小
                let vector_in_re = new Vec3(tempStartPoint.x-endPoint.x,tempStartPoint.y-endPoint.y,tempStartPoint.z-endPoint.z)
                let radian = Vec3.angle(vector_in_re,hitNormal)
                let hit_half = Math.cos(radian)*UtilTool.getDistance(new Vec2(tempStartPoint.x,tempStartPoint.z), new Vec2(endPoint.x,endPoint.z))
                let all_hit = hit_half * 2
                hitNormal.x = all_hit*hitNormal.x
                hitNormal.z = all_hit*hitNormal.z

                // 入射向量+法向量等于反射向量
                let vector_out = vector_in.add(hitNormal)
                vector_out.y = 0
                this.isDrawLine = false
                this.drawRayCast(endPoint, vector_out)
            }
        }
    }

    /**
     * @description 检测射线技术(有虚拟白球)
     * @param startLocation 起始位置 世界坐标系
     * @param vector_dir 方向向量
     * @param isShowVirtualBall 是否展示虚拟球
     */
    private drawRayCast_virtual(startPoint:Vec3, vector_dir:Vec3, isShowVirtualBall:boolean=false) {
        if (vector_dir.x==0 && vector_dir.z==0) return  // 点击起点不用处理

        let tempStartPoint = new Vec3(startPoint.x, startPoint.y, startPoint.z) // 临时复制一个起始点(因为startPoint总共起始点用作射线检测后会变成检测后中点的位置，还不知道为啥会变)
        let outRay = new geometry.Ray()
        let endPoint = null // 射线第一个碰撞体的3D世界坐标
        let hitNormal = null // 法线
        let distanceAB = 100000000  // 开始点距离射线检测点的距离,取最近的一个作为目标点(初始无限大)
        let colliderName = ""
        
        // 根据起点和方向创建射线
        outRay = new geometry.Ray(startPoint.x, startPoint.y, startPoint.z, vector_dir.x, vector_dir.y, vector_dir.z);
        if (PhysicsSystem.instance.raycast(outRay)) {
            const raycastResults = PhysicsSystem.instance.raycastResults;
            for (let i = 0; i < raycastResults.length; i++) {
                const item = raycastResults[i];
                if (item.collider.node.name == "wall" || item.collider.node.name == "edge_role" || item.collider.node.name == "edge1" || item.collider.node.name == "edge2") {
                    let tempDis = UtilTool.getDistance(new Vec2(startPoint.x, startPoint.z), new Vec2(item.hitPoint.x, item.hitPoint.z))
                    if (tempDis<distanceAB) {
                        endPoint = item.hitPoint
                        hitNormal = item.hitNormal  // 法线(单位向量)
                        distanceAB = tempDis
                        colliderName = item.collider.node.name
                    }
                }
            }
        }

        // 有检测到射中目标
        if (hitNormal) {
            // 先画当前瞄准线
            this.drawAimLine(this.World3DToLocal(tempStartPoint),this.World3DToLocal(endPoint),isShowVirtualBall)

            // 这里是球打到墙壁后反射出去的瞄准线
            let gameView = App.Model.gameView as GameBaseModel
            if (isShowVirtualBall && (colliderName != "edge_role" || !gameView.ballHoleOpen)) {
                // 入射向量
                let vector_in:Vec3 = new Vec3(endPoint.x-tempStartPoint.x, tempStartPoint.y, endPoint.z-tempStartPoint.z)

                // 这里计算法向量大小
                let vector_in_re = new Vec3(tempStartPoint.x-endPoint.x,tempStartPoint.y-endPoint.y,tempStartPoint.z-endPoint.z)
                let radian = Vec3.angle(vector_in_re,hitNormal)
                let hit_half = Math.cos(radian)*UtilTool.getDistance(new Vec2(tempStartPoint.x,tempStartPoint.z), new Vec2(endPoint.x,endPoint.z))
                let all_hit = hit_half * 2
                hitNormal.x = all_hit*hitNormal.x
                hitNormal.z = all_hit*hitNormal.z

                // 入射向量+法向量等于反射向量
                let vector_out = vector_in.add(hitNormal)
                vector_out.y = 0

                this.drawRayCast_virtual(endPoint, vector_out, false)
            }
        }
    }

    /**
     * @description 计算有虚拟白球的坐标 A白球的位置 B触摸点的位置 C其他球的位置 D:虚拟白球的位置 E:C点距离AB点最近的点位置
     * @param startPoint 开始位置点 
     * @param touchPoint 触摸位置点 
     */
    private calculateVirtualWball(startPoint:Vec3, touchPoint:Vec3) {
        let A:Vec2 = this.World3DToLocal(startPoint)
        let B:Vec2 = this.World3DToLocal(touchPoint)
        
        // let otherBall = null
        let lenAD = 100000000  // 白球到虚拟白球的距离(初始无限大)
        let ballZJ = App.Model.gameManage.BALL_DIA_2D
        let otherBall:Node = null
        App.Model.gameManage.otherBallArr.forEach(ball => {
            let C = this.World3DToLocal(ball.worldPosition) // 其它球位置点
            let lenAB = UtilTool.getDistance(A,B)
            let lenBC = UtilTool.getDistance(B,C)
            let lenAC = UtilTool.getDistance(A,C)

            let P = (lenAB + lenBC + lenAC) / 2 // ABC三点的周长的一半
            let S = Math.sqrt(P*(P-lenAB)*(P-lenBC)*(P-lenAC)) // ABC三点的面积
            let lenCE = S*2/lenAB   // 指定其他球距离射线端的最短距离，如果这个距离小于或者等于球的直径，则判定为两个球接触了

            // 这里检测这个碰撞球是不是指定方向有效的,角度小于90度则算作有效的球
            let vector_AC = new Vec3(ball.worldPosition.x-startPoint.x, 0, ball.worldPosition.z-startPoint.z) // A-C向量
            let vector_AB = new Vec3(touchPoint.x-startPoint.x,0,touchPoint.z-startPoint.z) // A-B向量
            let radian = Vec3.angle(vector_AC, vector_AB)   // 两向量的弧度
            let angleABC = radian*180/Math.PI   // 两向量的角度

            if (lenCE<=ballZJ && angleABC<=90 && Math.abs(ball.worldPosition.y-startPoint.y)<2) {
                let AE = Math.sqrt(Math.pow(lenAC, 2) - Math.pow(lenCE, 2))
                let DE = Math.sqrt(Math.pow(ballZJ, 2) - Math.pow(lenCE, 2))
                let AD = AE - DE
                if (lenAD > AD) {
                    lenAD = AD
                    otherBall = ball
                }
            }
        })

        if (otherBall) { // 有碰撞体
            // 有碰撞体并且是在射线指定的方向上
            let endPointD = UtilTool.getPointMove3(A,this.cueAngle,lenAD)    // D的本地坐标位置
            let worldPoint_D = this.localToWorld3D(endPointD)
            worldPoint_D.y = startPoint.y

            this.drawAimLine(A, endPointD, true, otherBall)

            if (this.lineType==2) { // 白球的反射瞄准线
                let vector = new Vec3(worldPoint_D.x-otherBall.worldPosition.x, 0, worldPoint_D.z-otherBall.worldPosition.z)
                this.drawRayCast_virtual(worldPoint_D, vector, false)
            } 
            else if (this.lineType==3) { // 其他球的反射瞄准线
                let vector = new Vec3(otherBall.worldPosition.x - worldPoint_D.x, 0, otherBall.worldPosition.z - worldPoint_D.z)
                this.drawRayCast_virtual(otherBall.worldPosition, vector, false)
            }
        }
        else {
            // 没有碰撞体，直接射到墙上
            let vector = new Vec3(touchPoint.x-startPoint.x, 0, touchPoint.z-startPoint.z)
            this.drawRayCast_virtual(startPoint, vector, true)
        }
    }

    /**
     * @description 画瞄准线
     * @param startLocation 起始位置 本地坐标系
     * @param endLocation 结束位置 本地坐标系
     * @param isShowVirtualBall 是否展示虚拟球
     * @param otherBall 瞄准线射到的球
     */
    private drawAimLine(startLocation: Vec2, endLocation: Vec2, isShowVirtualBall:boolean=false, otherBall=null) {
        
        if(!this.isMine) {
            return
        }

        // 每根准描线的长度
        const oneLineLen = 8 
        // 每跟准描线的间距
        const lineDis = 10
        // 两点间的总长度  
        let allLen = UtilTool.getDistance(startLocation, endLocation)
        if (allLen>2436) return

        // 两点间准描线的个数
        let total_count = Math.floor(allLen/(oneLineLen+lineDis))
        // 两点间的角度
        let angle = this.getAngleByUI(startLocation, endLocation)
        // 通过角度和一定的距离获取点
        let linePoint = UtilTool.getPointMove(angle,oneLineLen)
        // 通过角度和一定的距离获取点
        let linePoine2 = UtilTool.getPointMove(angle,oneLineLen+lineDis)

        let startX = startLocation.x
        let startY = startLocation.y
        let endX = startLocation.x + linePoint.x
        let endY = startLocation.y + linePoint.y 

        // 第二次反射线控制长度
        let limitLen = 2 + this.getSightLineLen() // 瞄准线限制长度(2这个是在球体上的隐藏的两节)
        if (!isShowVirtualBall) {
            if (total_count>limitLen) {
                total_count = limitLen
            }
        }

        let startReduce = 2
        if (total_count<=2) {
            startReduce = 1
        }
        // 画线
        for (let index = 0; index < total_count; index++) {
            if (index>=startReduce && (!isShowVirtualBall || index<total_count-1)) { // 这里前面少画两条线，后面少画一条，为了让线不穿过球
                // UtilTool.printLog("cjw test",startX,startY,endX,endY)
                this.graphic_line.moveTo(startX , startY)
                this.graphic_line.lineTo(endX, endY);
                // this.graphic_line.lineTo(endLocation.x, endLocation.y);
            }
            
            startX = startX + linePoine2.x
            startY = startY + linePoine2.y
            endX = endX + linePoine2.x
            endY = endY + linePoine2.y
        }
        this.graphic_line.stroke()

        // 显示虚拟球
        if (isShowVirtualBall) {
            this.virtualball.active = true
            let worldPos = this.localToWorld3D(endLocation)
            let startPoint = this.whiteBall.worldPosition
            worldPos.y = startPoint.y
            this.virtualball.setWorldPosition(worldPos)

            
            // 下面逻辑是显示虚拟白球还是显示禁止标签---------------------->
            // 如果一开始球体还没有分配归属，则显示虚拟白球
            let isHasSetBallBelogn = true
            if (App.fightMode == App.fightMode_2){
                let gameView = App.Model.gameView as GameModel2
                let gameLogic = gameView.gameLogic
                if (gameLogic) { 
                    isHasSetBallBelogn = gameLogic.isHasSetBallBelogn
                }
            }

            let isShowModelBan = false  // 是否展示虚拟白球，还是展示禁止标签
            if (otherBall) {
                let number = otherBall.getComponent(Ball).number
                let ballUI = App.Model.gameManage.otherBallArrUI[number-1]
                if (ballUI && !ballUI.active && isHasSetBallBelogn) {
                    isShowModelBan = true
                }
            }

            this.virtualball.getChildByName("model").active = !isShowModelBan
            this.virtualball.getChildByName("model_ban").active = isShowModelBan
            // <----------------------
        }
    }

    /**获取球杆瞄准线长度 */
    getSightLineLen() {
        let limitLen = 0
        if (App.fightMode == App.fightMode_1 || App.fightMode == App.fightMode_2) { // 幸运台球、练习场
            if (this.isMine) {
                limitLen = PD.mineCueInfo.sightLine
            }else{
                limitLen = PD.defaultCueInfo.sightLine
            }
        }
        else {
            let gameLogic = App.Model.gameLogic as GameBaseLogic

            if (gameLogic==null || gameLogic.curPlayUser==null) {
                // 这种情况是还没有初始化完界面的时候走了这里
                if (this.isMine) {
                    limitLen = PD.mineCueInfo.sightLine
                }else{
                    limitLen = PD.defaultCueInfo.sightLine
                }
            }else{
                let curPlayUser: PlayBaseUser = gameLogic.curPlayUser
                limitLen = App.Model.gameManage.getPlayerCueInfo(curPlayUser.userAddress).sightLine
            }
        }

        if (limitLen<0) limitLen = 0

        return limitLen
    }

    /**获取球杆最大力度 */
    getCueMaxForce() {
        let vigor = 0
        if (App.fightMode == App.fightMode_1 || App.fightMode == App.fightMode_2) { // 幸运台球、练习场
            if (this.isMine) {
                vigor = PD.mineCueInfo.vigor
            }else{
                vigor = PD.defaultCueInfo.vigor
            }
        }
        else {
            let gameLogic = App.Model.gameLogic as GameBaseLogic

            if (gameLogic==null || gameLogic.curPlayUser==null) {
                // 这种情况是还没有初始化完界面的时候走了这里
                if (this.isMine) {
                    vigor = PD.mineCueInfo.vigor
                }else{
                    vigor = PD.defaultCueInfo.vigor
                }
            }else{
                let curPlayUser: PlayBaseUser = gameLogic.curPlayUser
                vigor = App.Model.gameManage.getPlayerCueInfo(curPlayUser.userAddress).vigor
            }
        }

        return vigor
    }

    /** 球杆角度转换为球角度 */
    cueToBallAngle(cueAngle) {
        // return UtilTool.getRAngle(90-cueAngle)
        return UtilTool.getRAngle(-UtilTool.getRAngle(cueAngle))
    }

    /** 球角度转换为球杆角度 */
    ballToCueAngle(ballAngle) {
        // return UtilTool.getRAngle(90-ballAngle)
        return UtilTool.getRAngle(-UtilTool.getRAngle(ballAngle))
    }
}
